package me.ydq.core.sql;

import me.ydq.convert.ColumnConvert;
import me.ydq.core.SqlCriteria;
import me.ydq.dialect.Dialect;
import org.springframework.lang.NonNull;
import org.springframework.util.StringUtils;

import java.util.Collection;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * sql 条件构建类，根据不同的sql语句构建不同的 sql 条件片段
 */
public interface Sql {

    <T> String build(Dialect dialect, ColumnConvert convert, SqlCriteria<T> criterion);

    String EMPTY         = "";
    String BLANK         = " ";
    String BRACKET_LEFT  = " ( ";
    String BRACKET_RIGHT = " ) ";
    String BRACKET       = " (  ) ";
    String COMMA         = ",";
    String OR            = " OR ";
    String AND           = " AND ";
    String WHERE         = " WHERE ";
    String HAVING        = " HAVING ";
    String ASC           = " ASC ";
    String DESC          = " DESC ";
    String DELETE        = "DELETE FROM ";

    Pattern ALIAS_PATTERN = Pattern.compile(
            "\\bAS\\b\\s+(?:`[^`]*`|\"[^\"]*\"|'[^']*'|\\[[^]]*]|[a-zA-Z_][a-zA-Z0-9_]*)(?=(?:[^']*'[^']*')*[^']*$)",
            Pattern.CASE_INSENSITIVE);

    /**
     * 去除SQL中的所有AS别名
     */
    static String removeAliases(String sql) {
        return ALIAS_PATTERN.matcher(sql).replaceAll(EMPTY);
    }


    static String SELECT(@NonNull String column) {
        return "SELECT " + column + " FROM ";
    }

    static String SELECT_COUNT(@NonNull String column) {
        return "SELECT COUNT(" + column + ") FROM ";
    }

    static String UPDATE(String table, String sets, String condition) {
        String sql = "UPDATE " + table + " SET " + sets;
        if (HAS_CONDITION(condition)) {
            sql += BLANK + condition;
        }
        return sql;
    }

    static String ORDER_BY(Collection<String> orderBy) {
        if (orderBy == null || orderBy.isEmpty()) {
            return EMPTY;
        }

        return " ORDER BY " + String.join(COMMA, orderBy);
    }

    static String GROUP_BY(Collection<String> groupBy) {
        if (groupBy == null || groupBy.isEmpty()) {
            return EMPTY;
        }

        return " GROUP BY " + String.join(COMMA, groupBy);
    }

    static boolean HAS_CONDITION(String condition) {
        return StringUtils.hasText(condition)
                && !WHERE.equals(condition)
                && !HAVING.equals(condition)
                && !BRACKET.equals(condition);
    }

    static <T> String column(Dialect dialect, ColumnConvert convert, SqlCriteria<T> criterion) {
        return criterion.rawSqlColumn() != null ? criterion.rawSqlColumn() : dialect.wordWrap(convert.convert(criterion.cls(), criterion.column()));
    }

    static <T> String BETWEEN(Dialect dialect, ColumnConvert convert, SqlCriteria<T> criterion) {
        return column(dialect, convert, criterion) + " BETWEEN :" + criterion.param() + " AND :" + criterion.param(1);
    }

    static <T> String EQ(Dialect dialect, ColumnConvert convert, SqlCriteria<T> criterion) {
        return column(dialect, convert, criterion) + " = :" + criterion.param();
    }

    static <T> String GE(Dialect dialect, ColumnConvert convert, SqlCriteria<T> criterion) {
        return column(dialect, convert, criterion) + " >= :" + criterion.param();
    }

    static <T> String GT(Dialect dialect, ColumnConvert convert, SqlCriteria<T> criterion) {
        return column(dialect, convert, criterion) + " > :" + criterion.param();
    }

    static <T> String IN(Dialect dialect, ColumnConvert convert, SqlCriteria<T> criterion) {
        return column(dialect, convert, criterion) + " IN (:" + criterion.param() + ")";
    }

    static <T> String IS_NULL(Dialect dialect, ColumnConvert convert, SqlCriteria<T> criterion) {
        return column(dialect, convert, criterion) + " IS NULL";
    }

    static <T> String IS_NOT_NULL(Dialect dialect, ColumnConvert convert, SqlCriteria<T> criterion) {
        return column(dialect, convert, criterion) + " IS NOT NULL";
    }

    static <T> String LE(Dialect dialect, ColumnConvert convert, SqlCriteria<T> criterion) {
        return column(dialect, convert, criterion) + " <= :" + criterion.param();
    }

    static <T> String LIKE(Dialect dialect, ColumnConvert convert, SqlCriteria<T> criterion) {
        return column(dialect, convert, criterion) + " LIKE :" + criterion.param();
    }

    static <T> String LT(Dialect dialect, ColumnConvert convert, SqlCriteria<T> criterion) {
        return column(dialect, convert, criterion) + " < :" + criterion.param();
    }

    static <T> String NOT_BETWEEN(Dialect dialect, ColumnConvert convert, SqlCriteria<T> criterion) {
        return column(dialect, convert, criterion) + " NOT BETWEEN :" + criterion.param() + " AND :" + criterion.param(1);
    }

    static <T> String NOT_EQ(Dialect dialect, ColumnConvert convert, SqlCriteria<T> criterion) {
        return column(dialect, convert, criterion) + " <> :" + criterion.param();
    }

    static <T> String NOT_IN(Dialect dialect, ColumnConvert convert, SqlCriteria<T> criterion) {
        return column(dialect, convert, criterion) + " NOT IN (:" + criterion.param() + ")";
    }

    static <T> String NOT_LIKE(Dialect dialect, ColumnConvert convert, SqlCriteria<T> criterion) {
        return column(dialect, convert, criterion) + " NOT LIKE :" + criterion.param();
    }

    static <T> String OR(Dialect dialect, ColumnConvert convert, SqlCriteria<T> criterion) {
        if (criterion.params() == null || criterion.params().isEmpty()) {
            return EMPTY;
        }
        return criterion.params()
                .stream()
                .map(o -> {
                    if (o instanceof Collection<?> c) {
                        return c.stream()
                                .filter(cc -> cc instanceof SqlCriteria)
                                .map(SqlCriteria.class::cast)
                                .map(cc -> cc.toSql(dialect, convert))
                                .collect(Collectors.joining(AND, BRACKET_LEFT, BRACKET_RIGHT));
                    }
                    return EMPTY;
                })
                .filter(Sql::HAS_CONDITION)
                .collect(Collectors.joining(OR, BRACKET_LEFT, BRACKET_RIGHT));
    }
}
